/**
 * 自动化预约服务
 *
 * @fileoverview 提供自动化预约功能，包括浏览器控制、表单填写、预约流程执行等。
 * 支持批量预约处理、多种运行模式和详细的日志记录。
 *
 * 核心功能：
 * - 自动化预约流程执行
 * - 浏览器实例管理
 * - 批量预约处理
 * - 实时日志输出
 * - 错误处理和重试
 * - 运行状态管理
 *
 * @author System
 * @version 1.0.0
 * @since 2024
 */

import type { AppointmentManagement, AppointmentRecord } from '@shared/types/'
import { appendFile } from 'node:fs/promises'
import path from 'node:path'
import { normalizeAppointmentTime } from '@shared/utils/appointment-time'
import { getLogsPath } from '../config/index'
import { getLogger } from '../logger/index'
import { getAppointmentService } from './appointment-service'

/**
 * 自动化选项配置
 *
 * @interface AutomationOptions
 */
export interface AutomationOptions {
  /** 是否使用无头模式运行浏览器 */
  headless?: boolean
  /** 是否强制选择特定选项 */
  forceSelection?: boolean
  /** 强制指定的日期 */
  forceDate?: string
  /** 强制指定的时间 */
  forceTime?: string
  /**
   * 是否开启 24 小时无人值守模式。
   *
   * @description 启用后，服务会在后台循环拉取/复查可处理的预约记录并尝试预约，
   * 周期性执行直到调用 stopAutomation() 主动停止。前端调用 start 时会立即返回，
   * 实际处理进度通过日志通道通知渲染进程。
   */
  unattended?: boolean
  /**
   * 无人值守模式下每轮间隔秒数。
   *
   * @default 60
   * @minimum 15
   */
  unattendedIntervalSeconds?: number
  /**
   * 无人值守模式下同一轮处理相邻两条记录之间的间隔秒数。
   *
   * @default 5
   * @minimum 1
   */
  unattendedPerRecordSeconds?: number
  /**
   * 无人值守模式处理范围。
   * - 'selected': 仅处理本次前端传入的已选择记录（会在每轮前查询其最新状态）。
   * - 'pending_failed': 每轮自动抓取状态为 pending/failed 的记录。
   */
  unattendedScope?: 'selected' | 'pending_failed'
  // 监控配置（刻度调度、中午固定窗口）
  scheduleEnabled?: boolean
  scheduleMinutesInHour?: number[]
  scheduleGuardSeconds?: number
  middayReleaseEnabled?: boolean
  middayReleaseWeekdays?: number[]
  middayIntervalSeconds?: number
  intervalInsideWindowSeconds?: number
  intervalOutsideWindowSeconds?: number
  maxSleepBeforeWindowSeconds?: number
}

/**
 * 自动化请求数据
 *
 * @interface AutomationRequest
 */
export interface AutomationRequest {
  /** 要处理的预约记录列表 */
  appointments: Array<AppointmentRecord | AppointmentManagement>
  /** 自动化运行选项 */
  options: AutomationOptions
}

/**
 * 自动化执行结果
 *
 * @interface AutomationResult
 */
export interface AutomationResult {
  /** 执行是否成功 */
  success: boolean
  /** 错误信息（如果失败） */
  error?: string
  /** 详细结果列表 */
  results?: any[]
}

/**
 * 自动化预约服务类
 *
 * @description 负责执行自动化预约流程的核心服务，管理浏览器实例、处理预约逻辑、
 * 提供实时状态反馈和日志记录。
 *
 * @class AutomationService
 * @example
 * ```typescript
 * const service = getAutomationService()
 *
 * // 启动自动化预约
 * const result = await service.startAutomation({
 *   appointments: [appointment1, appointment2],
 *   options: { headless: false, detailedLogs: true }
 * })
 *
 * if (result.success) {
 *   console.log('预约执行完成')
 * }
 * ```
 */
class AutomationService {
  /** 日志记录器实例 */
  private logger: any = null
  /** 服务运行状态 */
  private isRunning = false
  /** 用于外部取消（停止预约） */
  private abortController: AbortController | null = null
  /** 日志回调函数 */
  private logCallback?: (level: string, message: string) => void
  /** 中午窗口当日关闭截止时间（ms），用于“无号后降频直到当天结束” */
  private middayClosedUntilMs: number = 0

  /**
   * 获取日志实例（延迟初始化）
   *
   * @description 延迟初始化日志记录器，提供备选方案以确保日志功能可用
   *
   * @returns {any} 日志记录器实例
   * @private
   */
  private getLoggerInstance() {
    if (!this.logger) {
      try {
        this.logger = getLogger()
      }
      catch {
        // 如果日志系统未初始化，使用console作为备选
        this.logger = {
          info: console.log,
          warn: console.warn,
          error: console.error,
        }
      }
    }
    return this.logger
  }

  /**
   * 开始自动化预约流程
   *
   * @description 启动自动化预约流程，按顺序处理每个预约记录。
   * 支持实时状态反馈和错误处理，可通过 stopAutomation() 中断执行。
   *
   * @param {AutomationRequest} request - 自动化请求数据
   * @param {AppointmentRecord[]} request.appointments - 要处理的预约记录列表
   * @param {AutomationOptions} request.options - 运行选项配置
   *
   * @returns {Promise<AutomationResult>} 执行结果，包含成功状态和详细信息
   *
   * @throws {Error} 当服务正在运行时抛出错误
   *
   * @example
   * ```typescript
   * const result = await service.startAutomation({
   *   appointments: [
   *     { id: 1, full_name: '张三', ... },
   *     { id: 2, full_name: '李四', ... }
   *   ],
   *   options: {
   *     headless: false,
   *     detailedLogs: true,
   *     saveSnapshots: true
   *   }
   * })
   *
   * if (result.success) {
   *   console.log(`处理完成，成功: ${result.results?.filter(r => r.success).length}`)
   * }
   * ```
   */
  async startAutomation(request: AutomationRequest): Promise<AutomationResult> {
    if (this.isRunning) {
      throw new Error('自动化流程正在运行中，请先停止当前流程')
    }

    // 为本次运行创建新的取消控制器
    this.abortController = new AbortController()
    this.isRunning = true
    const logger = this.getLoggerInstance()
    logger.info('开始自动化预约流程', {
      appointmentCount: request.appointments.length,
      options: request.options,
    })
    this.emitLog('info', `启动预约：选中 ${request.appointments.length} 条，选项 ${JSON.stringify(request.options)}`)

    try {
      // 无人值守：后台循环执行，立即返回给前端
      if (request.options?.unattended) {
        this.emitLog('info', '无人值守模式已启动，系统将持续轮询并尝试预约（直到手动停止）')
        // 后台执行循环，不阻塞 IPC 调用
        void this.runUnattendedLoop(request).catch((err) => {
          const msg = err instanceof Error ? err.message : String(err)
          this.emitLog('error', `无人值守循环异常: ${msg}`)
          this.isRunning = false
        })
        return { success: true, results: [] }
      }

      const results: Array<{ appointmentId: number, success: boolean, error?: string, data?: any }> = []

      // 逐个处理预约数据
      for (let i = 0; i < request.appointments.length; i++) {
        if (!this.isRunning) {
          this.logger.warn('自动化流程已被停止')
          break
        }

        const appointment = request.appointments[i]
        this.emitLog('info', `开始处理第 ${i + 1}/${request.appointments.length} 条预约数据: ${
          (appointment as any).full_name
          || String((appointment as any).license_owner_name)
          || String((appointment as any).user_name)
          || String((appointment as any).id)
        }`)

        try {
          const result = await this.processAppointment(appointment as any, request.options)
          const metrics = (result as any)?.data?.data?.metrics || (result as any)?.data?.metrics || null
          results.push({
            appointmentId: (appointment as any).id,
            success: result.success,
            error: result.error,
            data: result.data,
          })

          if (result.success) {
            const displayName = (
              (appointment as any).full_name
              || (appointment as any).license_owner_name
              || (appointment as any).user_name
              || String((appointment as any).id)
            )
            // 从执行结果中提取参考号/日期/时段
            const ref = result.data?.referenceNumber || result.data?.data?.referenceNumber
            const selectedDate = result.data?.data?.selectedDate
            const selectedTimeslot = result.data?.data?.selectedTimeslot

            this.emitLog('success', `预约处理成功: ${displayName}${ref ? `，参考编号: ${ref}` : ''}${selectedDate ? `，日期: ${selectedDate}` : ''}${selectedTimeslot ? `，时段: ${selectedTimeslot}` : ''}`)

            // 持久化到数据库
            try {
              const svc = getAppointmentService()
              await svc.updateAppointment((appointment as any).id, {
                appointment_status: 'booked',
                reference_number: ref,
                appointment_date: selectedDate,
                appointment_time: normalizeAppointmentTime(selectedTimeslot),
                raw_response: JSON.stringify({
                  metrics: metrics || undefined,
                  selectedDate,
                  selectedTimeslot,
                  referenceNumber: ref,
                }),
              })
              // 校验写库是否生效，若未生效则补救一次
              try {
                const fresh = await svc.getAppointment((appointment as any).id)
                if (!(fresh && fresh.appointment_status === 'booked')) {
                  await svc.updateAppointment((appointment as any).id, {
                    appointment_status: 'booked',
                    reference_number: ref,
                    appointment_date: selectedDate,
                    appointment_time: normalizeAppointmentTime(selectedTimeslot),
                  } as any)
                }
              }
              catch {}
            }
            catch (persistErr) {
              const msg = persistErr instanceof Error ? persistErr.message : String(persistErr)
              this.emitLog('warn', `预约结果写库失败: ${msg}`)
            }
          }
          else {
            const displayName = (
              (appointment as any).full_name
              || (appointment as any).license_owner_name
              || (appointment as any).user_name
              || String((appointment as any).id)
            )
            this.emitLog('error', `预约处理失败: ${displayName} - ${result.error}`)
            // 失败也记录指标到原始响应，便于历史查看
            try {
              const svc = getAppointmentService()
              // 特判：512-E-0001 已存在同服务类型的预约 -> 直接将状态置为 booked，避免后续重复
              const alreadyBooked = typeof result.error === 'string' && /512-E-0001|已存在同服务类型的预约|already\s*book/i.test(result.error)
              if (alreadyBooked) {
                await svc.updateAppointment((appointment as any).id, { appointment_status: 'booked', error_message: result.error || '' } as any)
              }
              else {
                await svc.updateAppointment((appointment as any).id, {
                  raw_response: JSON.stringify({
                    metrics: metrics || undefined,
                    error: result.error,
                  }),
                  error_message: result.error,
                })
              }
            }
            catch {}
          }
        }
        catch (error) {
          const errorMsg = error instanceof Error ? error.message : String(error)
          this.emitLog('error', `处理预约时出错: ${
            (appointment as any).full_name
            || String((appointment as any).license_owner_name)
            || String((appointment as any).user_name)
            || String((appointment as any).id)
          } - ${errorMsg}`)
          results.push({
            appointmentId: (appointment as any).id,
            success: false,
            error: errorMsg,
          })
        }

        // 添加延迟避免过于频繁的请求
        if (i < request.appointments.length - 1) {
          const perRecordSec = Math.max(1, Number(request.options?.unattendedPerRecordSeconds) || 5)
          this.emitLog('info', `等待 ${perRecordSec} 秒后处理下一条数据...`)
          try {
            await this.delayCancellable(perRecordSec * 1000)
          }
          catch {
            // 被取消
            break
          }
        }
      }

      const successCount = results.filter(r => r.success).length
      const totalCount = results.length
      const failCount = totalCount - successCount
      this.emitLog('info', `自动化流程完成：成功 ${successCount}/${totalCount}，失败 ${failCount}`)
      return {
        // 仅当全部成功时返回 true，避免前端误判
        success: totalCount > 0 && successCount === totalCount,
        results,
      }
    }
    catch (error) {
      const errorMsg = error instanceof Error ? error.message : String(error)
      const logger = this.getLoggerInstance()
      logger.error('自动化流程执行失败', { error: errorMsg })
      this.emitLog('error', `自动化流程执行失败: ${errorMsg}`)
      return {
        success: false,
        error: errorMsg,
      }
    }
    finally {
      if (!request.options?.unattended) {
        this.isRunning = false
      }
    }
  }

  /**
   * 无人值守循环
   *
   * @description 根据配置在后台持续拉取或复查预约记录并尝试预约，直到 stopAutomation 被调用。
   */
  /**
   * 无人值守主循环
   *
   * @description 在后台持续执行：
   * 1) 依据 unattendedScope 拉取目标记录（已选择或 pending/failed）
   * 2) 逐条执行预约流程并写库
   * 3) 休眠 unattendedIntervalSeconds 后进入下一轮
   * 4) 直到 isRunning 被 stopAutomation() 置为 false
   */
  private async runUnattendedLoop(request: AutomationRequest): Promise<void> {
    const scope = request.options?.unattendedScope || 'selected'
    const svc = getAppointmentService()
    const opts = request.options || {}

    const computeWindow = () => {
      // 刻度调度（0/15/30/45）- 从 options 读取
      const scheduleEnabled: boolean = opts.scheduleEnabled !== false
      const scheduleMinutes: number[] = (Array.isArray(opts.scheduleMinutesInHour) && opts.scheduleMinutesInHour.length
        ? opts.scheduleMinutesInHour
        : [0, 15, 30, 45]).filter((x: any) => Number.isFinite(x) && x >= 0 && x < 60).sort((a, b) => a - b)
      const scheduleGuardSec: number = Math.max(0, Number(opts.scheduleGuardSeconds) || 20)
      // 中午强制窗口配置 - 从 options 读取
      const middayEnabled: boolean = opts.middayReleaseEnabled !== false
      const middayWeekdays: number[] = Array.isArray(opts.middayReleaseWeekdays) && opts.middayReleaseWeekdays.length ? opts.middayReleaseWeekdays : [1, 2, 3, 4, 5]
      // 已固定中午窗口时间段，忽略可配的小时/分钟
      const middayIntervalSec: number = Math.max(1, Number(opts.middayIntervalSeconds) || 2)

      const now = new Date()
      const dow = now.getDay()
      const normDow = dow === 0 ? 7 : dow

      // 计算中午强制窗口
      const middayStart = new Date(now)
      // 固定中午窗口：11:55 - 12:15（优先级最高，需配置启用）
      middayStart.setHours(11, 55, 0, 0)
      // 固定窗口时长 20 分钟
      const middayEnd = new Date(middayStart.getTime())
      middayEnd.setMinutes(middayEnd.getMinutes() + 20)
      const isMiddayWeekday = middayWeekdays.includes(normDow)
      const middayTimeActive = middayEnabled && isMiddayWeekday && now >= middayStart && now < middayEnd
      const middayClosed = this.middayClosedUntilMs > 0 && Date.now() < this.middayClosedUntilMs
      const inMidday = middayTimeActive && !middayClosed

      // 刻度“窗口”：在最近一个刻度±guard 秒内，视为窗口期（仅当启用刻度调度时）
      let inScheduleWindow = false
      let nextScheduleTs = 0
      if (scheduleEnabled && scheduleMinutes.length) {
        const current = new Date(now)
        // 计算本小时与下一小时的刻度时刻
        const candidates: number[] = []
        for (const m0 of scheduleMinutes) {
          const t = new Date(current)
          t.setSeconds(0, 0)
          t.setMinutes(m0)
          candidates.push(t.getTime())
        }
        // 下一小时
        for (const m0 of scheduleMinutes) {
          const t = new Date(current)
          t.setHours(t.getHours() + 1, m0, 0, 0)
          candidates.push(t.getTime())
        }
        // 找到离“现在”最近的刻度与下一个刻度
        let nearestDiff = Number.POSITIVE_INFINITY
        let nextTs = Number.POSITIVE_INFINITY
        for (const ts of candidates) {
          const d = ts - now.getTime()
          if (Math.abs(d) < Math.abs(nearestDiff)) {
            nearestDiff = d
          }
          if (d >= 0 && d < nextTs - now.getTime()) {
            nextTs = ts
          }
        }
        if (!Number.isFinite(nextTs) || nextTs <= 0) {
          const t = new Date(current)
          t.setHours(t.getHours() + 1, scheduleMinutes[0], 0, 0)
          nextTs = t.getTime()
        }
        nextScheduleTs = nextTs
        inScheduleWindow = Math.abs(nearestDiff) <= scheduleGuardSec * 1000
      }

      // 组合窗口：中午窗口/刻度窗口
      const inWindow = inMidday || inScheduleWindow

      // 已不再计算标准窗口与热身

      // 计算下一个中午强制窗口开始时间（今天或之后最近的工作日）
      const nextMidday = new Date(middayStart)
      // 仅当“已过中午窗口结束”才推进到下一工作日；若当前时间仍在中午前，则保留今天12:00
      const afterMiddayEnd = now >= middayEnd
      if (!middayTimeActive && afterMiddayEnd)
        nextMidday.setDate(nextMidday.getDate() + 1)
      while (true) {
        const d2 = nextMidday.getDay()
        const n2 = d2 === 0 ? 7 : d2
        if (middayWeekdays.includes(n2))
          break
        nextMidday.setDate(nextMidday.getDate() + 1)
      }

      // 选择更近的重要时间点（中午窗口开始 / 刻度刻）
      const importantCandidates: number[] = []
      if (nextMidday.getTime() > now.getTime())
        importantCandidates.push(nextMidday.getTime())
      if (scheduleEnabled && nextScheduleTs > now.getTime())
        importantCandidates.push(nextScheduleTs)
      const nextImportant = importantCandidates.length ? Math.min(...importantCandidates) : now.getTime() + 60 * 1000

      // 计算休眠时长；若不在窗口/热身，则等待到下一个关键点
      const rawSleepMs = Math.max(0, inWindow ? 0 : (nextImportant - now.getTime()))

      // 应用"最大非窗口等待上限"（来自 options），避免一次性长睡眠
      const maxCapSec = Math.max(0, Number(opts.maxSleepBeforeWindowSeconds) || 0)
      const sleepMs = maxCapSec > 0 ? Math.min(rawSleepMs, maxCapSec * 1000) : rawSleepMs

      // 调试日志：详细输出窗口计算（仅在关键边界或每次调用时）
      try {
        const capped = sleepMs !== rawSleepMs
        this.emitLog('info', `窗口计算: now=${now.toISOString()} 中午窗口=[${new Date(middayStart).toISOString()} ~ ${new Date(middayEnd).toISOString()}] active=${middayTimeActive} closed=${this.middayClosedUntilMs > Date.now()} inMidday=${inMidday} | 刻度: enabled=${scheduleEnabled} guard=${scheduleGuardSec}s nextTick=${nextScheduleTs ? new Date(nextScheduleTs).toISOString() : 'n/a'} inSchedule=${inScheduleWindow} | 下次关键点=${new Date(nextImportant).toISOString()} sleepMs=${sleepMs}${capped ? `（原始=${rawSleepMs}ms，已按上限截断 ${maxCapSec}s）` : ''}`)
      }
      catch {}

      return { inWindow, sleepMs, middayActive: inMidday, middayIntervalSec, middayTimeActive, scheduleEnabled, nextScheduleTs }
    }
    // 刻度窗口内“无号尝试”计数与窗口进入状态
    let noSlotAttemptsThisWindow = 0
    let wasInWindow = false

    while (this.isRunning) {
      try {
        const w = computeWindow()
        // 进入新窗口则重置计数；离开窗口也重置
        if (w.inWindow && !wasInWindow)
          noSlotAttemptsThisWindow = 0
        if (!w.inWindow)
          noSlotAttemptsThisWindow = 0
        wasInWindow = w.inWindow
        const baseIntervalSec = Math.max(0, Number(request.options?.unattendedIntervalSeconds) || 0)
        const insideInterval = Math.max(1, Number(opts.intervalInsideWindowSeconds) || 5)
        const outsideInterval = Math.max(baseIntervalSec, Number(opts.intervalOutsideWindowSeconds) || 600)
        const effectiveInsideInterval = w.middayActive ? Math.max(1, w.middayIntervalSec) : insideInterval

        // 若仍在窗口且已达3次“无号”尝试，则等待到下一刻度
        if (w.inWindow && !w.middayActive && noSlotAttemptsThisWindow >= 3) {
          const nextTickMs = w.nextScheduleTs && w.nextScheduleTs > Date.now() ? (w.nextScheduleTs - Date.now()) : 60 * 1000
          const maxSegmentSec = 120
          const waitSec = Math.ceil(nextTickMs / 1000)
          this.emitLog('info', `本窗口已连续 3 次未发现可预约，等待到下一刻度（约 ${waitSec} 秒）`)
          let remainingMs = Math.max(1000, nextTickMs)
          while (remainingMs > 0 && this.isRunning) {
            const segMs = Math.min(remainingMs, maxSegmentSec * 1000)
            try {
              await this.delayCancellable(segMs)
            }
            catch {
              break
            }
            if (!this.isRunning)
              break
            const w2 = computeWindow()
            // 一旦离开当前窗口则提前结束，交由下一轮处理
            if (!w2.inWindow)
              break
            remainingMs = Math.max(0, (w2.nextScheduleTs && w2.nextScheduleTs > Date.now()) ? (w2.nextScheduleTs - Date.now()) : 0)
          }
          continue
        }

        if (!w.inWindow) {
          // 分段等待：避免一次性长睡眠，周期性复算窗口状态
          let remainingMs = Math.max(1000, w.sleepMs)
          const maxSegmentSec = 120 // 每段不超过120秒
          const firstSleepSec = Math.ceil(remainingMs / 1000)
          if (firstSleepSec >= 3600)
            this.emitLog('warn', `无人值守：非窗口期长等待预警，预计 ${firstSleepSec} 秒后进入窗口/热身（约 ${(firstSleepSec / 3600).toFixed(2)} 小时）；将分段等待<=${maxSegmentSec}秒并持续复查`)
          else
            this.emitLog('info', `无人值守：当前不在放号窗口，预计 ${firstSleepSec} 秒后进入窗口/热身；将分段等待<=${maxSegmentSec}秒并持续复查`)

          while (remainingMs > 0 && this.isRunning) {
            const segMs = Math.min(remainingMs, maxSegmentSec * 1000)
            try {
              await this.delayCancellable(segMs)
            }
            catch {
              break
            }
            if (!this.isRunning)
              break
            // 复算窗口：如已进入窗口/热身则提前结束；否则更新剩余时间
            const w2 = computeWindow()
            if (w2.inWindow)
              break
            remainingMs = Math.max(0, w2.sleepMs)
          }
          continue
        }

        let targets: any[] = []
        let processedCount = 0
        let roundAllNoSlot = true

        if (scope === 'pending_failed') {
          const list = await svc.getAppointmentsList(
            { appointment_status: ['pending', 'failed'] },
            { page: 1, page_size: 500, sort_field: 'updated_at', sort_order: 'desc' },
          )
          targets = (list?.appointments || [])
        }
        else {
          const ids = (request.appointments || []).map(a => (a as any).id).filter(Boolean)
          for (const id of ids) {
            const item = await svc.getAppointment(id)
            if (item && (item.appointment_status === 'pending' || item.appointment_status === 'failed')) {
              targets.push(item)
            }
          }
        }

        if (!this.isRunning)
          break

        if (!targets.length) {
          // 刻度调度：即便在“窗口外”，也应等待到下一刻度而非固定外部间隔
          let waitSec: number
          if (true) {
            if (w.inWindow) {
              waitSec = effectiveInsideInterval
            }
            else {
              const w2 = computeWindow()
              waitSec = Math.max(1, Math.ceil(w2.sleepMs / 1000))
            }
          }

          if (!w.inWindow && waitSec >= 3600) {
            this.emitLog('warn', `无人值守：无可处理记录，非窗口期等待过长预警：${waitSec} 秒（约 ${(waitSec / 3600).toFixed(2)} 小时），inWindow=${w.inWindow} insideInt=${effectiveInsideInterval} outsideInt=${outsideInterval} baseInt=${baseIntervalSec}`)
          }
          else {
            this.emitLog('info', `无人值守：当前没有可处理的预约记录，${waitSec} 秒后重试（inWindow=${w.inWindow} insideInt=${effectiveInsideInterval} outsideInt=${outsideInterval} baseInt=${baseIntervalSec}）`)
          }
          try {
            await this.delayCancellable(waitSec * 1000)
          }
          catch {
            break
          }
          continue
        }

        this.emitLog('info', `无人值守：本轮待处理 ${targets.length} 条记录`)

        const isNoSlotError = (msg?: string) => {
          if (!msg)
            return false
          const m = String(msg)
          return /无可用日期|没有可用日期|无可用时段|没有可用时段|该日期没有可用时段|当前没有可用的预约日期|暂无可预约|已约满/.test(m)
        }

        for (let i = 0; i < targets.length && this.isRunning; i++) {
          // 窗口强约束：每条记录处理前复算窗口，若已离开窗口（含中午固定窗口到点结束），则停止本窗口进一步尝试
          const wNow = computeWindow()
          if (!wNow.inWindow) {
            this.emitLog('info', '窗口已结束，停止本窗口进一步尝试，等待下一窗口触发')
            break
          }
          const appointment = targets[i]
          try {
            const result = await this.processAppointment(appointment as any, request.options)
            if (result.success) {
              const ref = result.data?.referenceNumber || result.data?.data?.referenceNumber
              const selectedDate = result.data?.data?.selectedDate
              const selectedTimeslot = result.data?.data?.selectedTimeslot
              const metrics = (result as any)?.data?.data?.metrics || (result as any)?.data?.metrics || null

              this.emitLog('success', `预约成功: ${appointment.full_name || appointment.user_name || appointment.id}${ref ? `，参考编号: ${ref}` : ''}`)
              try {
                await svc.updateAppointment(appointment.id, {
                  appointment_status: 'booked',
                  reference_number: ref,
                  appointment_date: selectedDate,
                  appointment_time: normalizeAppointmentTime(selectedTimeslot),
                  raw_response: JSON.stringify({
                    metrics: metrics || undefined,
                    selectedDate,
                    selectedTimeslot,
                    referenceNumber: ref,
                  }),
                })
                // 校验写库是否生效
                try {
                  const fresh = await svc.getAppointment(appointment.id)
                  if (!(fresh && fresh.appointment_status === 'booked')) {
                    await svc.updateAppointment(appointment.id, {
                      appointment_status: 'booked',
                      reference_number: ref,
                      appointment_date: selectedDate,
                      appointment_time: normalizeAppointmentTime(selectedTimeslot),
                    } as any)
                  }
                }
                catch {}
              }
              catch (persistErr) {
                const msg = persistErr instanceof Error ? persistErr.message : String(persistErr)
                this.emitLog('warn', `结果写库失败: ${msg}`)
              }
            }
            else {
              this.emitLog('warning', `预约失败: ${appointment.full_name || appointment.user_name || appointment.id} - ${result.error}`)
              // 统计“无号”失败
              if (!isNoSlotError(result.error))
                roundAllNoSlot = false
              try {
                const metrics = (result as any)?.data?.data?.metrics || (result as any)?.data?.metrics || null
                // 512-E-0001 已存在同服务类型预约 -> 直接标记 booked
                const alreadyBooked = typeof result.error === 'string' && /512-E-0001|已存在同服务类型的预约|already\s*book/i.test(result.error)
                if (alreadyBooked) {
                  await svc.updateAppointment(appointment.id, { appointment_status: 'booked', error_message: result.error || '' } as any)
                }
                else {
                  await svc.updateAppointment(appointment.id, {
                    raw_response: JSON.stringify({ metrics: metrics || undefined, error: result.error }),
                    error_message: result.error,
                  })
                }
              }
              catch {}
              // 刻度窗口内累计“无号”尝试计数（仅在窗口内且确认为“无号”时计数）
              if (wNow.inWindow && isNoSlotError(result.error)) {
                noSlotAttemptsThisWindow++
                this.emitLog('info', `窗口无号计数：${noSlotAttemptsThisWindow}/3（${wNow.middayActive ? '中午固定窗口' : '刻度窗口'}）`)
                // 达到3次立即结束本窗口处理（中午固定窗口不受此限制）
                if (!wNow.middayActive && noSlotAttemptsThisWindow >= 3) {
                  this.emitLog('info', '本窗口已尝试 3 次均无号，停止本窗口进一步尝试')
                  break
                }
              }
            }
          }
          catch (err) {
            const msg = err instanceof Error ? err.message : String(err)
            this.emitLog('error', `处理异常: ${appointment.id} - ${msg}`)
            // 异常视为非“无号”
            roundAllNoSlot = false
          }

          if (i < targets.length - 1 && this.isRunning) {
            const perRecordSec = Math.max(1, Number(request.options?.unattendedPerRecordSeconds) || 5)
            this.emitLog('info', `等待 ${perRecordSec} 秒后处理下一条...`)
            try {
              await this.delayCancellable(perRecordSec * 1000)
            }
            catch {
              break
            }
          }
          processedCount++
        }

        if (!this.isRunning)
          break

        const nextW = computeWindow()
        // 若处于中午窗口，且本轮处理过且全部失败为"无号"，则将中午窗口标记为当日关闭（降频到窗口外）
        if (w.middayActive && processedCount > 0 && roundAllNoSlot) {
          const endOfDay = new Date()
          endOfDay.setHours(23, 59, 59, 999)
          this.middayClosedUntilMs = endOfDay.getTime()
          this.emitLog('info', '中午窗口已无号，今日内将降频轮询')
        }

        // 检查是否还有待处理的失败记录（立即重试策略）
        let hasRemainingFailed = false
        if (scope === 'pending_failed') {
          const list = await svc.getAppointmentsList(
            { appointment_status: ['pending', 'failed'] },
            { page: 1, page_size: 500, sort_field: 'updated_at', sort_order: 'desc' },
          )
          hasRemainingFailed = (list?.appointments || []).length > 0
        }
        else {
          const ids = (request.appointments || []).map(a => (a as any).id).filter(Boolean)
          for (const id of ids) {
            const item = await svc.getAppointment(id)
            if (item && (item.appointment_status === 'pending' || item.appointment_status === 'failed')) {
              hasRemainingFailed = true
              break
            }
          }
        }

        // 如果有失败的记录，立即重试（只等待1秒避免CPU占用过高）
        // 如果没有失败的记录，才按照窗口期和间隔等待
        const effectiveInsideIntervalNext = nextW.middayActive ? Math.max(1, nextW.middayIntervalSec) : insideInterval
        const waitSec = hasRemainingFailed
          ? 1 // 有失败记录时立即重试（只等待1秒避免CPU占用过高）
          : (nextW.inWindow ? effectiveInsideIntervalNext : Math.max(1, Math.ceil(nextW.sleepMs / 1000)))

        if (hasRemainingFailed) {
          this.emitLog('info', `无人值守：检测到待处理记录，1秒后立即重试（已做反爬取处理，无需等待间隔）`)
        }
        else if (!nextW.inWindow && waitSec >= 3600) {
          this.emitLog('warn', `无人值守：本轮完成，非窗口期等待过长预警：${waitSec} 秒（约 ${(waitSec / 3600).toFixed(2)} 小时），next.inWindow=${nextW.inWindow} insideNext=${effectiveInsideIntervalNext} outside=${outsideInterval} base=${baseIntervalSec}`)
        }
        else {
          this.emitLog('info', `无人值守：本轮完成，${waitSec} 秒后开始下一轮（next.inWindow=${nextW.inWindow} insideNext=${effectiveInsideIntervalNext} outside=${outsideInterval} base=${baseIntervalSec}）`)
        }
        try {
          await this.delayCancellable(waitSec * 1000)
        }
        catch {
          break
        }
      }
      catch (loopErr) {
        const msg = loopErr instanceof Error ? loopErr.message : String(loopErr)
        const baseIntervalSec = Math.max(0, Number(request.options?.unattendedIntervalSeconds) || 0)
        const outsideInterval = Math.max(baseIntervalSec, Number(opts.intervalOutsideWindowSeconds) || 600)
        this.emitLog('error', `无人值守循环错误: ${msg}，${outsideInterval} 秒后重试`)
        try {
          await this.delayCancellable(outsideInterval * 1000)
        }
        catch {
          break
        }
      }
    }

    this.emitLog('warning', '无人值守模式已停止')
  }

  /**
   * 处理单个预约
   *
   * @description 处理单个预约记录的自动化流程，调用底层的自动化脚本执行实际预约操作。
   *
   * @param {AppointmentRecord} appointment - 预约记录数据
   * @param {AutomationOptions} options - 自动化选项配置
   * @returns {Promise<any>} 处理结果，包含成功状态和详细信息
   *
   * @throws {Error} 当自动化脚本执行失败时抛出错误
   * @private
   */
  private async processAppointment(appointment: AppointmentRecord, options: AutomationOptions): Promise<any> {
    try {
      // 动态导入自动化脚本
      const { runAutomationScript } = await import('../automation/scripts/automation-runner')

      // 适配数据为执行器期望结构
      const normalized = this.normalizeAppointmentForRunner(appointment as unknown as (AppointmentRecord | AppointmentManagement | any))
      const result = await runAutomationScript(normalized, {
        ...options,
        // 将取消信号传递给执行器，保证及时中断
        abortSignal: this.abortController?.signal,
        // 将日志回调桥接到 UI：由 registerAutomationHandlers 注入的 logCallback 承载
        onLog: (level, message) => this.emitLog(level, message),
      })

      return {
        success: result.success,
        error: result.error,
        data: result,
      }
    }
    catch (error) {
      const errorMsg = error instanceof Error ? error.message : String(error)
      throw new Error(`自动化脚本执行失败: ${errorMsg}`)
    }
  }

  /**
   * 适配预约记录为执行器期望字段
   *
   * @description 前端可能传入数据库风格字段（full_name / passport_number 等），
   * 执行器则期望 IPC 风格字段（user_name / user_passport 等）。
   */
  private normalizeAppointmentForRunner(
    raw: AppointmentRecord | AppointmentManagement | any,
  ): AppointmentRecord & { office_code?: string, appointment_date?: string, appointment_time?: string } {
    const normalized: any = {
      id: raw.id,
      user_passport: raw.user_passport ?? raw.passport_number ?? '',
      user_license: raw.user_license ?? raw.license_number ?? '',
      user_name: raw.user_name ?? raw.full_name ?? raw.license_owner_name ?? '',
      user_email: raw.user_email ?? raw.email ?? '',
      user_phone: raw.user_phone ?? raw.phone_number ?? undefined,
      issuing_country: raw.issuing_country ?? raw.issuing_country_code ?? 'CN',
      issuing_authority: raw.issuing_authority ?? raw.issuing_authority_code ?? 'GD',
      service_type: raw.service_type ?? 'DI',
      office_code: raw.office_code ?? 'HKLO',
      appointment_date: raw.appointment_date ?? '',
      appointment_time: normalizeAppointmentTime(raw.appointment_time ?? ''),
      timeslot_id: raw.timeslot_id ?? undefined,
      status: raw.status ?? raw.appointment_status ?? 'pending',
      booking_mode: raw.booking_mode ?? 'standard',
      created_at: raw.created_at ?? Date.now(),
      updated_at: raw.updated_at ?? Date.now(),
    }
    return normalized
  }

  /**
   * 停止自动化流程
   *
   * @description 优雅地停止正在运行的自动化流程。当前正在处理的预约会完成，
   * 但不会处理后续的预约记录。
   *
   * @returns {Promise<object>} 停止操作的结果
   * @returns {boolean} returns.success - 是否成功停止
   * @returns {string} [returns.error] - 错误信息（如果失败）
   *
   * @example
   * ```typescript
   * // 停止正在运行的自动化流程
   * const result = await service.stopAutomation()
   * if (result.success) {
   *   console.log('自动化流程已停止')
   * }
   * ```
   */
  async stopAutomation(): Promise<{ success: boolean, error?: string }> {
    try {
      // 先发出取消信号，立即中断任何正在等待/执行的步骤
      try { this.abortController?.abort() }
      catch {}
      this.isRunning = false
      this.emitLog('warning', '自动化流程已停止')

      const logger = this.getLoggerInstance()
      logger.info('自动化流程已停止')
      return { success: true }
    }
    catch (error) {
      const errorMsg = error instanceof Error ? error.message : String(error)
      const logger = this.getLoggerInstance()
      logger.error('停止自动化流程失败', { error: errorMsg })
      return { success: false, error: errorMsg }
    }
  }

  /**
   * 设置日志回调函数
   *
   * @description 设置日志事件的回调函数，用于将日志信息实时传递给前端界面。
   *
   * @param {Function} callback - 日志回调函数
   * @param {string} callback.level - 日志级别（info/warn/error/success）
   * @param {string} callback.message - 日志消息内容
   *
   * @example
   * ```typescript
   * service.setLogCallback((level, message) => {
   *   console.log(`[${level.toUpperCase()}] ${message}`)
   *   // 发送到前端UI更新
   *   window.webContents.send('automation:log', level, message)
   * })
   * ```
   */
  setLogCallback(callback: (level: string, message: string) => void) {
    this.logCallback = callback
  }

  /**
   * 发送日志事件
   *
   * @description 通过回调函数发送日志事件到前端，如果没有设置回调则忽略。
   *
   * @param {string} level - 日志级别
   * @param {string} message - 日志消息
   * @private
   */
  private emitLog(level: string, message: string) {
    // 1) 发给渲染进程
    if (this.logCallback)
      this.logCallback(level, message)

    // 2) 写入主日志文件（通过 winston）
    try {
      const logger = this.getLoggerInstance()
      const lvl = (level || '').toLowerCase()
      if (lvl === 'error')
        logger.error(message, { category: 'automation-ui' })
      else if (lvl === 'warn' || lvl === 'warning')
        logger.warn(message, { category: 'automation-ui' })
      else if (lvl === 'success')
        logger.info(message, { category: 'automation-ui', success: true })
      else
        logger.info(message, { category: 'automation-ui' })
    }
    catch {}

    // 3) 追加写入 JSONL 文件，便于离线查看
    try {
      const filePath = path.join(getLogsPath(), 'automation-ui.jsonl')
      const record = { t: Date.now(), level, message }
      void appendFile(filePath, `${JSON.stringify(record)}\n`, 'utf8')
    }
    catch {}
  }

  /**
   * 延迟执行
   *
   * @description 异步延迟指定毫秒数，用于控制操作间隔。
   *
   * @param {number} ms - 延迟时间（毫秒）
   * @returns {Promise<void>} 延迟 Promise
   * @private
   */
  private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms))
  }

  /**
   * 可中断延迟：当 stopAutomation() 调用后，立即拒绝等待
   */
  private delayCancellable(ms: number): Promise<void> {
    const signal = this.abortController?.signal
    if (!signal)
      return this.delay(ms)
    return new Promise((resolve, reject) => {
      let timer: any
      const cleanup = () => {
        try { clearTimeout(timer) }
        catch {}
      }
      const onAbort = () => {
        cleanup()
        reject(new Error('已取消'))
      }
      timer = setTimeout(() => {
        cleanup()
        resolve()
      }, ms)
      // once:true 已确保自动解除监听，这里无需在 cleanup 中显式移除
      try { signal.addEventListener('abort', onAbort as any, { once: true } as any) }
      catch {}
    })
  }

  /**
   * 获取服务运行状态
   *
   * @description 获取自动化服务的当前运行状态信息。
   *
   * @returns {{ isRunning: boolean }} 状态信息对象
   *
   * @example
   * ```typescript
   * const status = service.getStatus()
   * if (status.isRunning) {
   *   console.log('自动化服务正在运行')
   * }
   * ```
   */
  getStatus() {
    return {
      isRunning: this.isRunning,
    }
  }
}

// ============================================================================
// 全局服务实例管理
// ============================================================================

/** 全局自动化服务实例 */
let automationService: AutomationService | null = null

/**
 * 获取自动化服务实例
 *
 * @description 获取全局唯一的自动化服务实例，采用单例模式确保状态一致性。
 * 首次调用时会自动创建实例。
 *
 * @returns {AutomationService} 自动化服务实例
 *
 * @example
 * ```typescript
 * // 获取服务实例
 * const automationService = getAutomationService()
 *
 * // 设置日志回调
 * automationService.setLogCallback((level, message) => {
 *   console.log(`[${level}] ${message}`)
 * })
 *
 * // 启动自动化预约
 * const result = await automationService.startAutomation({
 *   appointments: appointmentList,
 *   options: { headless: false }
 * })
 * ```
 */
export function getAutomationService(): AutomationService {
  if (!automationService) {
    automationService = new AutomationService()
  }
  return automationService
}
